/*
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package com.sun.corba.se.spi.monitoring;

import java.util.*;

/**
 * <p>
 *
 * @author Hemanth Puttaswamy
 * </p>
 * <p>
 * StatisticsAccumulator accumulates the samples provided by the user and
 * computes the value of minimum, maximum, sum and sample square sum. When
 * the StatisticMonitoredAttribute calls getValue(), it will compute all
 * the statistics for the collected samples (Which are Minimum, Maximum,
 * Average, StandardDeviation) and provides a nice printable record as a
 * String.
 * <p>
 * Users can easily extend this class and provide the implementation of
 * toString() method to format the stats as desired. By default all the stats
 * are printed in a single line.
 * </p>
 */
public class StatisticsAccumulator {

    ///////////////////////////////////////
    // attributes


    // Users can extend this class to get access to current Max value
    protected double max = Double.MIN_VALUE;

    // Users can extend this class to get access to current Min value
    protected double min = Double.MAX_VALUE;

    private double sampleSum;

    private double sampleSquareSum;

    private long sampleCount;

    protected String unit;


    ///////////////////////////////////////
    // operations


    /**
     * <p>
     * User will use this method to just register a sample with the
     * StatisticsAccumulator. This is the only method that User will use to
     * expose the statistics, internally the StatisticMonitoredAttribute will
     * collect the information when requested from the ASAdmin.
     * </p>
     * <p>
     *
     * </p>
     * <p>
     *
     * @param value a double value to make it more precise
     *              </p>
     */
    public void sample(double value) {
        sampleCount++;
        if (value < min) min = value;
        if (value > max) max = value;
        sampleSum += value;
        sampleSquareSum += (value * value);
    } // end sample


    /**
     * Computes the Standard Statistic Results based on the samples collected
     * so far and provides the complete value as a formatted String
     */
    public String getValue() {
        return toString();
    }

    /**
     * Users can extend StatisticsAccumulator to provide the complete
     * Stats in the format they prefer, if the default format doesn't suffice.
     */
    public String toString() {
        return "Minimum Value = " + min + " " + unit + " " +
                "Maximum Value = " + max + " " + unit + " " +
                "Average Value = " + computeAverage() + " " + unit + " " +
                "Standard Deviation = " + computeStandardDeviation() + " " + unit +
                " " + "Samples Collected = " + sampleCount;
    }

    /**
     * If users choose to custom format the stats.
     */
    protected double computeAverage() {
        return (sampleSum / sampleCount);
    }


    /**
     * We use a derived Standard Deviation formula to compute SD. This way
     * there is no need to hold on to all the samples provided.
     * <p>
     * The method is protected to let users extend and format the results.
     */
    protected double computeStandardDeviation() {
        double sampleSumSquare = sampleSum * sampleSum;
        return Math.sqrt(
                (sampleSquareSum - ((sampleSumSquare) / sampleCount)) / (sampleCount - 1));
    }

    /**
     * <p>
     * Construct the Statistics Accumulator by providing the unit as a String.
     * The examples of units are &quot;Hours&quot;, &quot;Minutes&quot;,
     * &quot;Seconds&quot;, &quot;MilliSeconds&quot;, &quot;Micro Seconds&quot;
     * etc.,
     * </p>
     * <p>
     *
     * @param unit a String representing the units for the samples collected
     *             </p>
     * @return a StatisticsAccumulator with ...
     * </p>
     * <p>
     */
    public StatisticsAccumulator(String unit) {
        this.unit = unit;
        sampleCount = 0;
        sampleSum = 0;
        sampleSquareSum = 0;
    }


    /**
     * Clears the samples and starts fresh on new samples.
     */
    void clearState() {
        min = Double.MAX_VALUE;
        max = Double.MIN_VALUE;
        sampleCount = 0;
        sampleSum = 0;
        sampleSquareSum = 0;
    }

    /**
     * This is an internal API to test StatisticsAccumulator...
     */
    public void unitTestValidate(String expectedUnit, double expectedMin,
                                 double expectedMax, long expectedSampleCount, double expectedAverage,
                                 double expectedStandardDeviation) {
        if (!expectedUnit.equals(unit)) {
            throw new RuntimeException(
                    "Unit is not same as expected Unit" +
                            "\nUnit = " + unit + "ExpectedUnit = " + expectedUnit);
        }
        if (min != expectedMin) {
            throw new RuntimeException(
                    "Minimum value is not same as expected minimum value" +
                            "\nMin Value = " + min + "Expected Min Value = " + expectedMin);
        }
        if (max != expectedMax) {
            throw new RuntimeException(
                    "Maximum value is not same as expected maximum value" +
                            "\nMax Value = " + max + "Expected Max Value = " + expectedMax);
        }
        if (sampleCount != expectedSampleCount) {
            throw new RuntimeException(
                    "Sample count is not same as expected Sample Count" +
                            "\nSampleCount = " + sampleCount + "Expected Sample Count = " +
                            expectedSampleCount);
        }
        if (computeAverage() != expectedAverage) {
            throw new RuntimeException(
                    "Average is not same as expected Average" +
                            "\nAverage = " + computeAverage() + "Expected Average = " +
                            expectedAverage);
        }
        // We are computing Standard Deviation from two different methods
        // for comparison. So, the values will not be the exact same to the last
        // few digits. So, we are taking the difference and making sure that
        // the difference is not greater than 1.
        double difference = Math.abs(
                computeStandardDeviation() - expectedStandardDeviation);
        if (difference > 1) {
            throw new RuntimeException(
                    "Standard Deviation is not same as expected Std Deviation" +
                            "\nStandard Dev = " + computeStandardDeviation() +
                            "Expected Standard Dev = " + expectedStandardDeviation);
        }
    }


} // end StatisticsAccumulator
